package com.tianquan.DistributedLock;

import com.tianquan.DistributedLock.entiy.Lock;
import com.tianquan.DistributedLock.entiy.LockEvent;
import com.tianquan.DistributedLock.listener.LockRenewalListener;
import com.tianquan.mapper.LockMapper;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Semaphore;

/**
 * @BelongsProject: uploaddemo
 * @BelongsPackage: com.tianquan.DistributedLock
 * @Author: tianquan
 * @CreateTime: 2024-09-25  09:06
 * @Description: db锁
 * @Version: 1.0
 */

@Component
public class DataBaseLock implements ApplicationContextAware {

    /**
     * 锁的过期时间，单位为毫秒
     */
    private static final long LOCK_EXPIRATION_TIME = 30 * 1000;

    /**
     * 时区
     */
    private static final ZoneId ZONE_ID = ZoneId.systemDefault();

    /**
     * 重入锁 加锁 加一
     */
    private static final Boolean RELOCKED = true;

    /**
     * 重入锁 释放锁 减一
     */
    private static final Boolean UNLOCK = false;

    /**
     * 信号量
     */
    private final ConcurrentHashMap<String, Semaphore> SEMAPHORE_MAP;

    /**
     * 信号量大小
     */
    private static final int SEMAPHORE_SIZE = 1;

    /**
     * 自旋次数
     */
    private static final int SPIN_COUNT = 3;

    private ApplicationContext applicationContext;

    private final LockMapper lockMapper;

    public DataBaseLock(LockMapper lockMapper) {
        this.lockMapper = lockMapper;
        SEMAPHORE_MAP = new ConcurrentHashMap<>(128);
    }

    /**
     * 获取锁
     *
     * @param lockName 锁的key
     * @return 是否获取成功
     */
    public boolean lock(String lockName) {
        while (true) {
            // 获取锁信息
            Lock lock = lockMapper.isExist(lockName);
            // 判断是否是线程再次加锁
            if (lock != null) {
                if (isReentrant(lockName, lock)) break;
                // 锁是否过期 过期将锁删除
                isOverdue(lockName, lock);
            }
            // 尝试加锁，成功直接返回
            if (tryLock(lockName)) break;
            // 加锁失败并且自旋次数为0，基于信号量等待
            await(lockName);
        }
        return true;
    }

    /**
     * @param lockName 锁的key
     * @return boolean
     * @description: 尝试加锁
     * @author: tianquan
     * @date: 2024/11/24
     **/
    public Boolean tryLock(String lockName) {
        // 默认自旋3次
        int spinCount = SPIN_COUNT;
        while (spinCount > 0) {
            // 创建锁对象
            Lock lock = Lock.createLock(lockName, Thread.currentThread().getId(), get30SecondsTimestamp());
            try {
                // 加锁
                lockMapper.lock(lock);
            } catch (Exception e) {
                // 加锁失败，自旋次数减一
                spinCount--;
                continue;
            }
            // 加锁成功,给锁发布续期的任务
            applicationContext.publishEvent(new LockEvent(this, lock));
            // 占用信号量
            try {
                Semaphore semaphore = new Semaphore(SEMAPHORE_SIZE);
                semaphore.acquire();
                SEMAPHORE_MAP.put(lockName, semaphore);
                // 返回加锁成功
                return true;
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return false;
    }

    /**
     * @param lockName 锁的key
     * @param lock     锁信息
     * @description: 是否是锁重入
     * @author: tianquan
     * @date: 2024/11/24
     * @return: boolean
     **/
    public Boolean isReentrant(String lockName, Lock lock) {
        // 有锁判断是否是锁重入
        if (lock.getThread() == Thread.currentThread().getId()) {
            // 锁重入，计数加一
            lockMapper.lockReentrant(lockName, RELOCKED);
            return true;
        }
        return false;
    }

    /**
     * @param lockName 锁的key
     * @param lock     锁信息
     * @description: 锁是否过期
     * @author: tianquan
     * @date: 2024/11/24
     * @return: void
     **/
    public void isOverdue(String lockName, Lock lock) {
        // 有锁且不是锁重入,判断锁是否过期
        if (lock.getExpirationTime() < getTimestampOfDateTime(LocalDateTime.now())) {
            // 锁过期,直接将锁删除
            lockMapper.delete(lockName);
        }
    }

    /**
     * 信号量等待
     *
     * @param lockName 锁的key
     */
    public void await(String lockName) {
        try {
            SEMAPHORE_MAP.get(lockName).acquire();
            // 等待锁释放,继续尝试获取锁
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 释放锁
     *
     * @param lockName 锁的key
     * @return 是否释放成功
     */
    public boolean unLock(String lockName) {
        // 获取锁信息
        Lock lock = lockMapper.isExist(lockName);
        // 有锁判断是否是重入锁
        if (lock.getValue() > 1 && lock.getThread() == Thread.currentThread().getId()) {
            // 锁重入 计数减1
            lockMapper.lockReentrant(lockName, UNLOCK);
            return true;
        }
        // 优先删除锁的续期任务
        // LockRenewalTask.localMap.remove(lockName);
        LockRenewalListener.deleteRenewalTask(Thread.currentThread().getId());
        // 判断是否删除成功
        if (lockMapper.unlock(lockName, Thread.currentThread().getId()) != 1) {
            // 释放锁失败，再将任务添加回去
            applicationContext.publishEvent(new LockEvent(this, lock));
            return false;
        }
        // 返还信号量
        SEMAPHORE_MAP.get(lockName).release();
        return true;
    }

    /**
     * @param localDateTime 时间
     * @description: 时间戳转时间
     * @author: tianquan
     * @date: 2024/11/24
     * @return: long
     **/
    public static long getTimestampOfDateTime(LocalDateTime localDateTime) {
        return localDateTime.atZone(ZONE_ID).toInstant().toEpochMilli();
    }

    /**
     * @description: 获取30秒后的时间戳
     * @author: tianquan
     * @date: 2024/11/24
     * @return: long
     **/
    public static long get30SecondsTimestamp() {
        return getTimestampOfDateTime(LocalDateTime.now().plus(LOCK_EXPIRATION_TIME, ChronoUnit.MILLIS));
    }

    /**
     * @param timestamp 时间戳
     * @description: 时间戳转时间
     * @author: tianquan
     * @date: 2024/11/24
     * @return: java.time.LocalDateTime
     **/
    public static LocalDateTime parseDefault(long timestamp) {
        return LocalDateTime.ofInstant(Instant.ofEpochSecond(timestamp), ZoneId.systemDefault());
    }

    @Override
    public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
